#include <sdk.h>
#include "crc32.h"
#include "menuitemsmanager.h"
#include "genericmultilinenotesdlg.h"
#include "sqplus.h"
#include "scriptbindings.h"
#include "sc_plugin.h"
#include "sqstdstring.h"

template<> ScriptingManager* Mgr<ScriptingManager>::instance = nullptr;
template<> bool  Mgr<ScriptingManager>::isShutdown = false;

static wxString s_ScriptErrors;
static wxString capture;

void PrintSquirrelToWxString( wxString& msg, const SQChar* s, va_list& vl ) {
  int buffer_size = 2048;
  SQChar* tmp_buffer;
  for( ;; buffer_size *= 2 ) {
    tmp_buffer = new SQChar [buffer_size];
    int retvalue = vsnprintf( tmp_buffer, buffer_size, s, vl );
    if( retvalue < buffer_size ) {
      msg = cbC2U( tmp_buffer );
      delete[] tmp_buffer;
      break;
    }
    delete[] tmp_buffer;
  }
}

static void ScriptsPrintFunc( HSQUIRRELVM, const SQChar * s, ... ) {
  va_list vl;
  va_start( vl, s );
  wxString msg;
  PrintSquirrelToWxString( msg, s, vl );
  va_end( vl );
  s_ScriptErrors << msg;
}

static void CaptureScriptOutput( HSQUIRRELVM, const SQChar * s, ... ) {
  va_list vl;
  va_start( vl, s );
  wxString msg;
  PrintSquirrelToWxString( msg, s, vl );
  ::capture.append( msg );
  va_end( vl );
}

BEGIN_EVENT_TABLE( ScriptingManager, wxEvtHandler )
END_EVENT_TABLE()

ScriptingManager::ScriptingManager()
  : m_AttachedToMainWindow( false ),
    m_MenuItemsManager( false ) {
  SquirrelVM::Init( ( SquirrelInitFlags )( sqifAll & ~sqifIO ) );
  if( !SquirrelVM::GetVMPtr() ) {
    m_Throw( _T( "无法创建脚本引擎!" ) );
  }
  sq_setprintfunc( SquirrelVM::GetVMPtr(), ScriptsPrintFunc );
  sqstd_register_stringlib( SquirrelVM::GetVMPtr() );
  RefreshTrusts();
  ScriptBindings::RegisterBindings();
}

ScriptingManager::~ScriptingManager() {
  ConfigManagerContainer::StringToStringMap myMap;
  int i = 0;
  TrustedScripts::iterator it;
  for( it = m_TrustedScripts.begin(); it != m_TrustedScripts.end(); ++it ) {
    if( !it->second.permanent ) {
      continue;
    }
    wxString key = F( _T( "trust%d" ), i++ );
    wxString value = F( _T( "%s?%x" ), it->first.c_str(), it->second.crc );
    myMap.insert( myMap.end(), std::make_pair( key, value ) );
  }
  Manager::Get()->GetConfigManager( _T( "security" ) )->Write( _T( "/trusted_scripts" ), myMap );
  SquirrelVM::Shutdown();
}

void ScriptingManager::RegisterScriptFunctions() {
}

bool ScriptingManager::LoadScript( const wxString& filename ) {
  wxLogNull ln;
  wxString fname( filename );
  wxFile f( fname );
  if( !f.IsOpened() ) {
    bool found = false;
    if( !m_CurrentlyRunningScriptFile.IsEmpty() ) {
      fname = wxFileName( m_CurrentlyRunningScriptFile ).GetPath() + _T( '/' ) + filename;
      f.Open( fname );
      found = f.IsOpened();
    }
    if( !found ) {
      fname = ConfigManager::LocateDataFile( filename, sdScriptsUser | sdScriptsGlobal );
      f.Open( fname );
      if( !f.IsOpened() ) {
        Manager::Get()->GetLogManager()->LogError( _T( "无法打开脚本 " ) + filename );
        return false;
      }
    }
  }
  wxString contents = cbReadFileContents( f );
  m_CurrentlyRunningScriptFile = fname;
  bool ret = LoadBuffer( contents, fname );
  m_CurrentlyRunningScriptFile.Clear();
  return ret;
}

bool ScriptingManager::LoadBuffer( const wxString& buffer, const wxString& debugName ) {
  wxString incName = UnixFilename( debugName );
  if( m_IncludeSet.find( incName ) != m_IncludeSet.end() ) {
    Manager::Get()->GetLogManager()->LogWarning( F( _T( "忽略包含(\"%s\") 因为它会导致递归..." ), incName.wx_str() ) );
    return true;
  }
  m_IncludeSet.insert( incName );
  s_ScriptErrors.Clear();
  SquirrelObject script;
  try {
    script = SquirrelVM::CompileBuffer( cbU2C( buffer ), cbU2C( debugName ) );
  } catch( SquirrelError e ) {
    msging( F( _T( "文件名: %s\n错误: %s\n细节: %s" ), debugName.c_str(), cbC2U( e.desc ).c_str(), s_ScriptErrors.c_str() ), _T( "脚本编译错误" ), wxICON_ERROR );
    m_IncludeSet.erase( incName );
    return false;
  }
  try {
    SquirrelVM::RunScript( script );
  } catch( SquirrelError e ) {
    msging( F( _T( "文件名: %s\n错误: %s\n细节: %s" ), debugName.c_str(), cbC2U( e.desc ).c_str(), s_ScriptErrors.c_str() ), _T( "脚本运行错误" ), wxICON_ERROR );
    m_IncludeSet.erase( incName );
    return false;
  }
  m_IncludeSet.erase( incName );
  return true;
}

wxString ScriptingManager::LoadBufferRedirectOutput( const wxString& buffer ) {
  s_ScriptErrors.Clear();
  ::capture.Clear();
  sq_setprintfunc( SquirrelVM::GetVMPtr(), CaptureScriptOutput );
  bool res = LoadBuffer( buffer );
  sq_setprintfunc( SquirrelVM::GetVMPtr(), ScriptsPrintFunc );
  return res ? ::capture : ( wxString ) wxEmptyString;
}

wxString ScriptingManager::GetErrorString( SquirrelError* exception, bool clearErrors ) {
  wxString msg;
  if( exception ) {
    msg << cbC2U( exception->desc );
  }
  msg << s_ScriptErrors;
  if( clearErrors ) {
    s_ScriptErrors.Clear();
  }
  return msg;
}

void ScriptingManager::DisplayErrors( SquirrelError* exception, bool clearErrors ) {
  wxString msg = GetErrorString( exception, clearErrors );
  if( !msg.IsEmpty() ) {
    GenericMultiLineNotesDlg dlg( Manager::Get()->GetAppWindow(), _T( "脚本错误" ), msg, true );
    dlg.ShowModal();
  }
}

void ScriptingManager::InjectScriptOutput( const wxString& output ) {
  s_ScriptErrors << output;
}

int ScriptingManager::Configure() {
  return -1;
}

bool ScriptingManager::RegisterScriptPlugin( const wxString&, const wxArrayInt& ids ) {
  if( !m_AttachedToMainWindow ) {
    Manager::Get()->GetAppWindow()->PushEventHandler( this );
    m_AttachedToMainWindow = true;
  }
  for( size_t i = 0; i < ids.GetCount(); ++i ) {
    Connect( ids[i], -1, wxEVT_COMMAND_MENU_SELECTED, ( wxObjectEventFunction )( wxEventFunction )( wxCommandEventFunction )
             &ScriptingManager::OnScriptPluginMenu );
  }
  return true;
}

bool ScriptingManager::RegisterScriptMenu( const wxString& menuPath, const wxString& scriptOrFunc, bool isFunction ) {
  if( !m_AttachedToMainWindow ) {
    Manager::Get()->GetAppWindow()->PushEventHandler( this );
    m_AttachedToMainWindow = true;
  }
  int id = wxNewId();
  id = m_MenuItemsManager.CreateFromString( menuPath, id );
  wxMenuItem* item = Manager::Get()->GetAppFrame()->GetMenuBar()->FindItem( id );
  if( item ) {
    if( !isFunction ) {
      item->SetHelp( _T( "单击此菜单项时按SHIFT键可在编辑器中编辑分配的脚本" ) );
    }
    Connect( id, -1, wxEVT_COMMAND_MENU_SELECTED, ( wxObjectEventFunction )( wxEventFunction )( wxCommandEventFunction )
             &ScriptingManager::OnScriptMenu );
    MenuBoundScript mbs;
    mbs.scriptOrFunc = scriptOrFunc;
    mbs.isFunc = isFunction;
    m_MenuIDToScript.insert( m_MenuIDToScript.end(), std::make_pair( id, mbs ) );
    Manager::Get()->GetLogManager()->Log( F( _T( "脚本/函数 '%s' 在菜单下注册 '%s'" ), scriptOrFunc.c_str(), menuPath.c_str() ) );
    return true;
  }
  Manager::Get()->GetLogManager()->Log( _T( "注册脚本菜单时出错: " ) + menuPath );
  return false;
}

bool ScriptingManager::UnRegisterScriptMenu( M_Code_unused const wxString& menuPath ) {
  Manager::Get()->GetLogManager()->DebugLog( _T( "ScriptingManager::UnRegisterScriptMenu() not implemented" ) );
  return false;
}

bool ScriptingManager::UnRegisterAllScriptMenus() {
  m_MenuItemsManager.Clear();
  return true;
}

bool ScriptingManager::IsScriptTrusted( const wxString& script ) {
  TrustedScripts::iterator it = m_TrustedScripts.find( script );
  if( it == m_TrustedScripts.end() ) {
    return false;
  }
  wxUint32 crc = wxCrc32::FromFile( script );
  if( crc == it->second.crc ) {
    return true;
  }
  msging( script + _T( "\n\n" ) + _T( "脚本被标记为 \"trusted\" 检测到被修改了脚本不再受信任." ),
          _T( "警告" ), wxICON_WARNING );
  m_TrustedScripts.erase( it );
  return false;
}

bool ScriptingManager::IsCurrentlyRunningScriptTrusted() {
  return IsScriptTrusted( m_CurrentlyRunningScriptFile );
}

void ScriptingManager::TrustScript( const wxString& script, bool permanently ) {
  TrustedScripts::iterator it = m_TrustedScripts.find( script );
  if( it != m_TrustedScripts.end() ) {
    m_TrustedScripts.erase( it );
  }
  TrustedScriptProps props;
  props.permanent = permanently;
  props.crc = wxCrc32::FromFile( script );
  m_TrustedScripts.insert( m_TrustedScripts.end(), std::make_pair( script, props ) );
}

void ScriptingManager::TrustCurrentlyRunningScript( bool permanently ) {
  TrustScript( m_CurrentlyRunningScriptFile, permanently );
}

bool ScriptingManager::RemoveTrust( const wxString& script ) {
  TrustedScripts::iterator it = m_TrustedScripts.find( script );
  if( it != m_TrustedScripts.end() ) {
    m_TrustedScripts.erase( it );
    return true;
  }
  return false;
}

void ScriptingManager::RefreshTrusts() {
  m_TrustedScripts.clear();
  ConfigManagerContainer::StringToStringMap myMap;
  Manager::Get()->GetConfigManager( _T( "security" ) )->Read( _T( "/trusted_scripts" ), &myMap );
  ConfigManagerContainer::StringToStringMap::iterator it;
  for( it = myMap.begin(); it != myMap.end(); ++it ) {
    wxString key = it->second.BeforeFirst( _T( '?' ) );
    wxString value = it->second.AfterFirst( _T( '?' ) );
    TrustedScriptProps props;
    props.permanent = true;
    unsigned long tmp;
    value.ToULong( &tmp, 16 );
    props.crc = tmp;
    m_TrustedScripts.insert( m_TrustedScripts.end(), std::make_pair( key, props ) );
  }
}

const ScriptingManager::TrustedScripts& ScriptingManager::GetTrustedScripts() {
  return m_TrustedScripts;
}

void ScriptingManager::OnScriptMenu( wxCommandEvent& event ) {
  MenuIDToScript::iterator it = m_MenuIDToScript.find( event.GetId() );
  if( it == m_MenuIDToScript.end() ) {
    msging( _T( "没有与此菜单关联的脚本?!?" ), _T( "错误" ), wxICON_ERROR );
    return;
  }
  MenuBoundScript& mbs = it->second;
  if( mbs.isFunc ) {
    try {
      SqPlus::SquirrelFunction<void> f( cbU2C( mbs.scriptOrFunc ) );
      f();
    } catch( SquirrelError exception ) {
      DisplayErrors( &exception );
    }
    return;
  }
  if( wxGetKeyState( WXK_SHIFT ) ) {
    wxString script = ConfigManager::LocateDataFile( mbs.scriptOrFunc, sdScriptsUser | sdScriptsGlobal );
    Manager::Get()->GetEditorManager()->Open( script );
    return;
  }
  try {
    if( !LoadScript( mbs.scriptOrFunc ) ) {
      msging( _T( "无法运行脚本: " ) + mbs.scriptOrFunc, _T( "错误" ), wxICON_ERROR );
    }
  } catch( SquirrelError exception ) {
    DisplayErrors( &exception );
  }
}

void ScriptingManager::OnScriptPluginMenu( wxCommandEvent& event ) {
  ScriptBindings::ScriptPluginWrapper::OnScriptMenu( event.GetId() );
}
